import { Row } from './row';
import { Config } from './config';
import { Injectable, InjectFactory, TFactory } from '../decorators/inject';
import { ATableDatas } from '../types/table-data.abstract';
import { MergeCell } from './merge-cell';
import { WholeCol } from './whole-col';
import { Cell } from './cell';
import { Transform, toJSON, classTransformToObj, objTransformToClass } from '../decorators/toJSON';
import { Hook } from './hook';
import { ParamsPrecondition } from '../decorators/params-precondition';
import { FullControl } from './full-control';
import { ITableDatas, ICellInfo } from './table-datas.interface';

@Injectable('TableDatas')
@Transform()
export class TableDatas extends ATableDatas implements ITableDatas{
    @toJSON([], {
        toObj: (rows: Row[]) => {
            let result: any[] = [];
            for(let i = 0; i < rows.length; i++) {
                if(rows[i]) {
                    let sampleRow = classTransformToObj(rows[i]);
                    if(Object.keys(sampleRow).length === 0) {
                        continue;
                    }
                    result.push(Object.assign(sampleRow, {row: i}));
                }
            }
            return result;
        },
        toCls: (instance: {factory: (arg: any) => any}, rows: any[]) => {
            let result: any[] = [];
            for(let row of rows) {
                let rowInstance = instance.factory(Row);
                objTransformToClass(rowInstance, row);
                result[row.row] = rowInstance;
            }
            return result;
        }
    })
    public rows: Row[] = [];
    public cols: WholeCol[] = []
    constructor(
        protected conf: Config, 
        // protected colHeader: ColHeader,
        public mergeCell: MergeCell,
        protected hook: Hook,
        protected fullControl: FullControl,
        @InjectFactory() public factory: TFactory
    ) {
        super();
        // @ts-ignore
        window.data = this;
    }
    public rowIsExist(row: number) {
        return Boolean(this.rows[row]);
    }
    public cellIsExist(row: number, col: number) {
        console.warn('未判断合并单元格的情况！');
        if(!this.rowIsExist(row)) {
            return false;
        } 
        return Boolean(this.rows[row].cells[col]);
    }
    public getCell(row: number, col: number, resEmptyCell: boolean = false): Cell|null {
        if(this.cellIsExist(row, col)) {
            return this.rows[row].cells[col];
        }
        return resEmptyCell ? null : this.factory(Cell);
    }
    public getCellType(row: number, col: number) {
        if(!this.cellIsExist(row, col)) {
            return this.fullControl.getCellType(row, col, null);
        }
        return this.rows[row].cells[col].type;
    }
    public getRowHeight(row: number): number {
        if(this.conf.hiddenRows.has(row)) {
            return 3 * this.conf.enlarge;
        }
        if(this.rows[row] == undefined) {
            return this.conf.header.row.height;
        } else {
            return this.rows[row].getActualHeight();
        }
    }
    public getColWidth(col: number): number {
        if(this.conf.hiddenCols.has(col)) {
            return 3 * this.conf.enlarge;
        }
        if(this.cols[col] == undefined) {
            return this.conf.header.col.width;
        } else {
            return this.cols[col].getWidth() * this.conf.enlarge;
        }
    }
    public getWholeCol(col: number): WholeCol {
        if(this.cols[col] == undefined) {
            return this.factory(WholeCol);
        } else {
            return this.cols[col];
        }
    }
    public setWholeCol(colData: WholeCol, col: number) {
        this.cols[col] = colData;
    }
    public hasRow(row: number) {
        return Boolean(this.rows[row])
    }
    public getRow(row: number): Row {
        if(this.rows[row] == undefined) {
            let rowInstance  = this.factory(Row) as Row;
            rowInstance.updatePosition(row);
            return rowInstance;
        } else {
            return this.rows[row];
        }
    }
    public setRow(rowInstance: Row, row: number) {
        this.rows[row] = rowInstance;
        this.hook.tableDataChange.excute();
    }
    public getFullCols(col: number) {
        let cells: ICellInfo[] = [];
        for(let i = 0; i < this.rows.length; i++) {
            if(this.rows[i] === undefined) {
                continue;
            }
            if(this.rows[i].cells[col] !== undefined) {
                cells.push({
                    row: i,
                    col: col,
                    cell: this.rows[i].cells[col]
                })
            }
        }
        return cells;
    }
    public getFullRows(row: number) {
        let rowInstance = this.getRow(row);
        let cells: ICellInfo[] = [];
        for(let i = 0; i < rowInstance.cells.length; i++) {
            if(rowInstance.cells[i] === undefined) {
                continue;
            }
            cells.push({
                row: row,
                col: i,
                cell: rowInstance.cells[i]
            });
        }
        return cells;
    }
    public setCell(row: number, col: number, cell: Cell) {
        let rowInstance = this.getRow(row);
        rowInstance.setCell(col, cell);
        this.rows[row] = rowInstance;
    }
    public setCellValue(row: number, col: number, value: BaseValueType) {
        let rowInstance = this.getRow(row);
        rowInstance.setCellValue(col, value);
        this.rows[row] = rowInstance;
    }
    public clearData(row: number, col: number): void {
        if(!this.cellIsExist(row, col)) {
            return;
        }
        if(this.rows[row].cells[col].value) {
            this.rows[row].cells[col].value.destroy();
        }
        this.rows[row].cells[col] = null;
    }
    public getMaxRow() {
        return this.rows.length - 1;
    }
    public getMaxCol(boundary?: {start: number, end: number}): number {
        let max = 0;
        if(boundary) {
            for(let i = boundary.start; i <= boundary.end; i++) {
                let rowInstance = this.getRow(i);
                let maxCol = rowInstance.getMaxCol();
                if(maxCol > max) {
                    max = maxCol;
                }
            }
        } else {
            for(let row of this.rows) {
                try {
                    if(!row) {
                        continue;
                    }
                    let colLen = row.getMaxCol();
                    if(colLen > max) {
                        max = colLen;
                    } 
                } catch (error) {
                    console.error(row, this.rows)
                }
    
            }
        }
        return max;
    }

    @ParamsPrecondition((rows: number[]) => [Array.from(new Set(rows))])
    public deleteRows(rows: number[]) {
        let result: any[] = [];
        for(let row of rows) {
            this.hook.beforeDeleteRow.excute(row, this.getRow(row));
            if(row <= this.getMaxRow()) {
                let rowValue = {};
                if(this.rows[row]) {
                    rowValue = classTransformToObj(this.rows[row]);
                }
                result.push(Object.assign(rowValue, {row: row}));
                this.rows.splice(row, 1);
            }
            this.hook.afterDeleteRow.excute(row, this.getRow(row));
        }
        return result;
    }

    @ParamsPrecondition((cols: number[]) => [Array.from(new Set(cols))])
    public deleteCols(cols: number[]) {
        let result: any[] = [];
        let _res: {[key: string]: any[]} = {};
        for(let col of cols) {
            this.hook.beforeDeleteCol.excute(col);
            // todo
            for(let i = 0; i < this.rows.length; i++) {
                if(this.rows[i] && this.rows[i].cells.length > col) {
                    let [cell] = this.rows[i].cells.splice(col, 1);
                    if(cell) {
                        if(!_res[i]) {
                            _res[i] = [];
                        }
                        cell.updateLocation({row: i, col: col})
                        _res[i].push(classTransformToObj(cell));
                    }
                }
            }
            this.hook.afterDeleteCol.excute(col);
        }
        for(let row in _res) {
            if(_res[row].length === 0) {
                continue;
            }
            result.push({
                row: Number(row),
                cells: _res[row]
            })
        }
        return result;
    }
}